package ru.unlocker.reports.domain;

import com.github.oxo42.stateless4j.StateMachine;
import com.github.oxo42.stateless4j.StateMachineConfig;
import java.util.Optional;
import java.util.function.BiFunction;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;

@Getter(AccessLevel.PACKAGE)
public class ReportStateMachine {

  public enum ReportState {
    STARTED_INACTIVE,
    FINISHED_INACTIVE,
    ERROR_INACTIVE,
    WAITING,
    STARTED_ACTIVE,
    TIMED_OUT,
    FINISHED_ACTIVE,
    ERROR_ACTIVE,
    FINISHED_TIMED_OUT
  }

  public enum ReportTrigger {
    REPORT_STARTED, REPORT_FINISHED, REPORT_FAILED, TIMER_ENABLED, TIMER_FIRED
  }

  private final StateMachineConfig<ReportState, ReportTrigger> stateMachineConfig;

  public ReportStateMachine() {
    StateMachineConfig<ReportState, ReportTrigger> config = new StateMachineConfig<>();
    config.configure(ReportState.STARTED_INACTIVE)
        .permit(ReportTrigger.REPORT_FINISHED, ReportState.FINISHED_INACTIVE)
        .permit(ReportTrigger.REPORT_FAILED, ReportState.ERROR_INACTIVE)
        .permit(ReportTrigger.TIMER_ENABLED, ReportState.STARTED_ACTIVE)
        .permit(ReportTrigger.TIMER_FIRED, ReportState.TIMED_OUT);
    config.configure(ReportState.FINISHED_INACTIVE)
        .permit(ReportTrigger.TIMER_ENABLED, ReportState.FINISHED_ACTIVE);
    config.configure(ReportState.ERROR_INACTIVE)
        .permit(ReportTrigger.REPORT_FINISHED, ReportState.STARTED_INACTIVE)
        .permit(ReportTrigger.TIMER_ENABLED, ReportState.ERROR_ACTIVE);
    config.configure(ReportState.WAITING)
        .permit(ReportTrigger.REPORT_STARTED, ReportState.STARTED_ACTIVE)
        .permit(ReportTrigger.REPORT_FINISHED, ReportState.FINISHED_ACTIVE)
        .permit(ReportTrigger.REPORT_FAILED, ReportState.ERROR_ACTIVE)
        .permit(ReportTrigger.TIMER_FIRED, ReportState.TIMED_OUT);
    config.configure(ReportState.STARTED_ACTIVE)
        .permit(ReportTrigger.TIMER_FIRED, ReportState.TIMED_OUT)
        .permit(ReportTrigger.REPORT_FINISHED, ReportState.FINISHED_ACTIVE)
        .permit(ReportTrigger.REPORT_FAILED, ReportState.ERROR_ACTIVE);
    config.configure(ReportState.TIMED_OUT)
        .permit(ReportTrigger.REPORT_FINISHED, ReportState.FINISHED_TIMED_OUT);
    config.configure(ReportState.ERROR_ACTIVE)
        .permit(ReportTrigger.REPORT_FINISHED, ReportState.FINISHED_ACTIVE)
        .permit(ReportTrigger.TIMER_FIRED, ReportState.TIMED_OUT);
    this.stateMachineConfig = config;
  }

  public Optional<ReportStatus> transfer(@NonNull ReportState initial, @NonNull ReportAndTimerEvent event) {
    StateMachine<ReportState, ReportTrigger> machine = new StateMachine<>(initial, getStateMachineConfig());
    machine.fire(TRIGGER_PRODUCER.apply(event.getReportEvent(), event.getTimerEvent()));
    ReportState newState = machine.getState();
    return (initial == newState)
        ? Optional.empty()
        : Optional.of(new ReportStatus(event.getReportCode(), event.getWorkday(), newState, event.getCreatedAt()));
  }

  static final BiFunction<ReportEventKind, TimerEventKind, ReportTrigger> TRIGGER_PRODUCER = (rep, tim) -> {
    if (rep == null) {
      return switch (tim) {
        case TIMER_ENABLED -> ReportTrigger.TIMER_ENABLED;
        case TIMER_FIRED -> ReportTrigger.TIMER_FIRED;
      };
    }
    return switch (rep) {
      case REPORT_STARTED -> ReportTrigger.REPORT_STARTED;
      case REPORT_FINISHED -> ReportTrigger.REPORT_FINISHED;
      case REPORT_FAILED -> ReportTrigger.REPORT_FAILED;
    };
  };
}
